/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */

#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "ns3/command-line.h"
#include "ns3/enum.h"
#include "ns3/config.h"
#include "ns3/boolean.h"
#include "ns3/packet-sink.h"
#include "ns3/names.h"
#include "ns3/histogram.h"
#include "complex-behavior-tools.h"
#include "ipv4-deflection-routing-helper.h"

#include <time.h>
#include <math.h>

using namespace ns3;
using namespace std;
using namespace eecityu;

NS_LOG_COMPONENT_DEFINE ("DeflectionComplexBehavior");

//
// Run one of the test suites.  Returns an integer with the boolean sense of
// "an error has occurred."  That is, 0 == false -> no error; 1 == true -> an
// error occurred.
//
int 
TestRunnerMain (int argc, char *argv[])
{
  bool doVerbose = false;
  bool doList = false;
  bool doMultiple = false;
  bool doHelp = false;
  bool doSuite = false;
  bool doKinds = false;
  bool doTest = false;

  bool haveBasedir = false;
  bool haveTempdir = false;
  bool haveOutfile = false;
  bool haveType = false;

  std::string suiteName;
  std::string basedir;
  std::string tempdir;
  std::string outfileName;
  std::string typeName;

  for (int i = 1; i < argc; ++i)
    {
      std::string arg(argv[i]);

      if (arg.find ("--test") != std::string::npos)
        {
          doTest = true;
          argv[i] = (char*) "";
        }

      if (arg.find ("--basedir=") != std::string::npos)
        {
          basedir = arg.substr (arg.find_first_of ("=") + 1, 9999);
          haveBasedir = true;
          argv[i] = (char*) "";
        }

      if (arg.find ("--constrain=") != std::string::npos)
        {
          typeName = arg.substr (arg.find_first_of ("=") + 1, 9999);
          haveType = true;
          argv[i] = (char*) "";
        }

      if (arg.compare ("--help") == 0)
        {
          doHelp = true;
          argv[i] = (char*) "";
        }

      if (arg.compare ("--kinds") == 0)
        {
          doKinds = true;
          argv[i] = (char*) "";
        }

      if (arg.compare ("--list") == 0)
        {
          doList = true;
          argv[i] = (char*) "";
        }

      if (arg.compare ("--multiple") == 0)
        {
          doMultiple = true;
          argv[i] = (char*) "";
        }

      if (arg.find ("--out=") != std::string::npos)
        {
          outfileName = arg.substr (arg.find_first_of ("=") + 1, 9999);
          haveOutfile = true;
          argv[i] = (char*) "";
        }

      if (arg.find ("--suite=") != std::string::npos)
        {
          suiteName = arg.substr (arg.find_first_of ("=") + 1, 9999);
          doSuite = true;
          argv[i] = (char*) "";
        }

      if (arg.find ("--tempdir=") != std::string::npos)
        {
          tempdir = arg.substr (arg.find_first_of ("=") + 1, 9999);
          haveTempdir = true;
          argv[i] = (char*) "";
        }

      if (arg.compare ("--verbose") == 0)
        {
          doVerbose = true;
          argv[i] = (char*) "";
        }
    }

  if (!doTest) 
    {
      return false;
    }

  NS_LOG_DEBUG ("Run test-runner");

  //
  // A help request trumps everything else.  If we have one, just print the help
  // and leave.
  //
  if (doHelp)
    {
      std::cout << "  --test:                 Run the embedded test-runner" << std::endl;
      std::cout << "  --basedir=dir:          Set the base directory (where to find src) to \"dir\"" << std::endl;
      std::cout << "  --tempdir=dir:          Set the temporary directory (where to find data files) to \"dir\"" << std::endl;
      std::cout << "  --constrain=test-type:  Constrain checks to test suites of type \"test-type\"" << std::endl;
      std::cout << "  --help:                 Print this message" << std::endl;
      std::cout << "  --kinds:                List all of the available kinds of tests" << std::endl;
      std::cout << "  --list:                 List all of the test suites (optionally constrained by test-type)" << std::endl;
      std::cout << "  --multiple:             Allow test suites and cases to produce multiple failures" << std::endl;
      std::cout << "  --out=file-name:        Set the test status output file to \"file-name\"" << std::endl;
      std::cout << "  --suite=suite-name:     Run the test suite named \"suite-name\"" << std::endl;
      std::cout << "  --verbose:              Turn on messages in the run test suites" << std::endl;

      exit (false);
    }

  //
  // A kinds request trumps everything remaining.  If we are asked, just 
  // print the list of types and leave.
  //
  if (doKinds)
    {
      //
      // Coming up with a string to represent a test type is completely up to
      // us here.  We just define the types as being a string composed of the
      // enum defined in test.h converted to lower case.
      //
      std::cout << "  bvt:         Build Verification Tests (to see if build completed successfully)" << std::endl;
      std::cout << "  core:        Run all TestSuite-based tests (exclude examples)" << std::endl;
      std::cout << "  example:     Examples (to see if example programs run successfully)" << std::endl;
      std::cout << "  performance: Performance Tests (check to see if the system is as fast as expected)" << std::endl;
      std::cout << "  system:      System Tests (spans modules to check integration of modules)" << std::endl;
      std::cout << "  unit:        Unit Tests (within modules to check basic functionality)" << std::endl;

      exit (false);
    }

  //
  // A list request is the first functional request.  It trumps running the
  // actual tests.  If we get a list request, we don't run anything, we just
  // do the requested list which may or may not be qualified by a typename.
  //
  if (doList)
    {
      for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
        {
          TestSuite *suite = TestRunner::GetTestSuite (i);

          //
          // Filter the tests listed by type if requested.  The special typeName 
          // "core" means any TestSuite.
          //
          if (haveType && typeName != "core")
            {
              TestSuite::TestType type = suite->GetTestType ();
              if (typeName == "bvt" && type != TestSuite::BVT)
                {
                  continue;
                }

              if (typeName == "unit" && type != TestSuite::UNIT)
                {
                  continue;
                }

              if (typeName == "system" && type != TestSuite::SYSTEM)
                {
                  continue;
                }

              if (typeName == "example" && type != TestSuite::EXAMPLE)
                {
                  continue;
                }

              if (typeName == "performance" && type != TestSuite::PERFORMANCE)
                {
                  continue;
                }
            }

          //
          // This creates a list of test suite names that can be used by the
          // high level test manager to get a list of all tests.  It will then
          // typically launch individual tests in parallel, calling back here
          // with a specific "suite=" to run.
          //
          std::cout << suite->GetName () << std::endl;
        }

      exit (false);
    }

  //
  // If we haven't been asked to run a test suite, we are just going to happily
  // try and run everything.  Test suites are possibly going to need to figure
  // out where there source directory is, and to do that they will need to know
  // where the base directory of the distribution is (the directory in which 
  // "src" is found).  We could try and run without it, but when it is needed,
  // the test will fail with an assertion.  So to be safe, we require a basedir
  // to proceed.
  //

  if (haveBasedir == false)
    {
      std::cout << "Must specify a base directory to run tests (use --basedir option)" << std::endl;
      exit (true);
    }

  //
  // If given an output file, we just append the output of each test suite 
  // we're asked to run to the end of that file.  We need to append since the
  // higher level test runner may be just running a number of tests back to 
  // back.  We leave it up to that code to decide how to deal with possible
  // parallel operation -- we just append to a file here.  If no output file
  // is specified, we don't do any output and just return the sense of error
  // given by the test.
  //
  std::ofstream *pofs = 0;
  std::ofstream ofs;

  if (!outfileName.empty ())
    {
      ofs.open (outfileName.c_str (), std::fstream::out | std::fstream::app);
      pofs = &ofs;
    }

  //
  // If we have a specified test suite to run, then we only run that suite.
  // The default case is to "run everything.  We don't expect this to be done
  // much since typically higher level code will be running suites in parallel
  // but we'll do it if asked.
  //
  bool result = false;
  bool suiteRan = false;

  for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
    {
      TestSuite *testSuite = TestRunner::GetTestSuite (i);
      if (doSuite == false || (doSuite == true && suiteName == testSuite->GetName ()))
        {
          testSuite->SetBaseDir (basedir);
          testSuite->SetTempDir (tempdir);
          testSuite->SetStream (pofs);
          testSuite->SetVerbose (doVerbose);
          testSuite->SetContinueOnFailure (doMultiple);
          result |= testSuite->Run ();
          suiteRan = true;
        }
    }

  ofs.close();

  //
  // If we couldn't figure out how to run at least one test, then return an error
  //
  if (suiteRan == false)
    {
      exit (true);
    }

  exit (result);
}

bool enableEcmpCmd = false;
bool enableDeflectionCmd = false;
string lineSpeedCmd = "10Mb/s";
double dataLinkUtilCmd = 0.1; // link utilization of data, which is a value larger than 0.0
double backgroundLinkUtil = 0.1; // link utilization of background traffic, which is a value between 0.0 and 1.0
uint32_t pktSizeCmd = 1000 - 30; // the header size is 30 bytes
uint32_t maxNumPacketsCmd = 0; // number of packets to generate. if set to 0, then it is unlimited
string lineDelayCmd = "0.8ms";
double endTimeCmd = 10.0;
uint32_t queueMaxPktCmd = 10;
uint32_t flowLengthCmd = 2; // the length of the grid flows
bool enableSqliteOutputCmd = false; // set to true and the results are wrote into a sqlite3 database file
bool outputTracesCmd = false; // set to true to output several trace files
uint32_t numTimeIntervalsCmd = 100; // divide the entire simulation previod into this number of intervals for counting events

/**
 * \brief one simulation pass is run by calling this function
 *
 * \param grid A squre grid topology with every node installed with Internet stack and 
 * routing tables is populated among nodes
 * \param n The number of rows or columns of the squre grid topology
 * \param dataRate The data rate of each traffic flow
 */
int 
RunSimulation (PointToPointGridHelper grid, uint32_t n, DataRate dataRate)
{
  // calculate flow rate and set the packet size
  uint32_t pktSize = pktSizeCmd; // size of each packet in byte
  DataRate lineSpeed (lineSpeedCmd); // the speed of every link
  // install virtical and horizontal flows to the grid topology
  uint32_t flowLen = flowLengthCmd;
  uint32_t numPkt = maxNumPacketsCmd;
  ApplicationContainer apps = InstallGridTraffic (grid, n, n, flowLen, dataRate, numPkt, pktSize);  
  // sort out the source applications and destination applications
  ApplicationContainer srcApps;
  ApplicationContainer dstApps;
  for (uint32_t i=0; i<apps.GetN ();) {
    srcApps.Add (apps.Get (i++));    
    dstApps.Add (apps.Get (i++));
  }
  NS_LOG_DEBUG ("Grid traffic installed and results " 
    << srcApps.GetN () << " source applications and " 
    << dstApps.GetN () << " destination applications");

  // set start and stop time for all application
  double stopTime = endTimeCmd; // the stopping time, in seconds, for the simulation
  srcApps.Start (Seconds (0));  
  srcApps.Stop (Seconds (stopTime));
  dstApps.Start (Seconds (0));

  // construct a flow monitor
  Ptr<FlowMonitor> flowmon;
  FlowMonitorHelper flowmonHelper;
  flowmon = flowmonHelper.InstallAll ();
  NS_LOG_DEBUG ("Flow monitors installed on all nodes");

  // print simulation summary
  NS_LOG_DEBUG ("packet size = " << pktSize);
  NS_LOG_DEBUG ("line speed = " << lineSpeedCmd);
  NS_LOG_DEBUG ("queue max packets = " << queueMaxPktCmd);
  NS_LOG_DEBUG ("data rate = " << dataRate);  
  NS_LOG_DEBUG ("simulation stop time = " << stopTime);
  NS_LOG_DEBUG ("Delfection is enable = " << enableDeflectionCmd);
  NS_LOG_DEBUG ("ECMP is enable = " << enableEcmpCmd);

  // Create a DataCollector object to hold information about this run.
  stringstream runId;
  stringstream input;
  runId << "run-" << time (NULL);
  input 
    << "DataRate=" << dataRate.GetBitRate () / (double) lineSpeed.GetBitRate ()
    << "; QueueMaxPackets=" << queueMaxPktCmd 
    << "; FlowLength=" << flowLengthCmd 
    << "; EndTime=" << endTimeCmd << ";";
  DataCollector data;
  data.DescribeRun("complex-behavior-in-deflection-routing",
                   "default",
                   input.str ().c_str (),
                   runId.str ().c_str ());
  data.AddMetadata ("author", "Wilson Thong <wilsonwk@ee.cityu.edu.hk>");
  data.AddMetadata ("LineSpeed (bps)", (double) lineSpeed.GetBitRate ());
  data.AddMetadata ("DataRate (bps)", (double) dataRate.GetBitRate ());
  data.AddMetadata ("ECMP enable", (uint32_t) enableEcmpCmd);
  data.AddMetadata ("Deflection enable", (uint32_t) enableDeflectionCmd);
  data.AddMetadata ("Number of nodes", n*n);
  data.AddMetadata ("Packet size (with L2 header)", pktSizeCmd+30);
  data.AddMetadata ("Number of packets per flow", maxNumPacketsCmd);
  data.AddMetadata ("Link delay", lineDelayCmd);
  data.AddMetadata ("Simulation end time (secs)", endTimeCmd);
  data.AddMetadata ("Queue buffer size (#packets)", queueMaxPktCmd);
  data.AddMetadata ("Flow length", flowLengthCmd);
  data.AddMetadata ("Use Sqlite3 output", (uint32_t) enableSqliteOutputCmd);
  data.AddMetadata ("Output trace files", (uint32_t) outputTracesCmd);
  data.AddMetadata ("Number of time intervals", numTimeIntervalsCmd);
  time_t runStartTime = time (NULL);
  string runStartTimeStr = ctime (&runStartTime);
  runStartTimeStr.erase (runStartTimeStr.find ('\n'));
  data.AddMetadata ("Simulation begins on", runStartTimeStr);

  if (outputTracesCmd) {
    // print simulation trace
    string traceFilename = "deflection-complex-behavior-" + runId.str () + ".tr";
    ofstream traceFile (traceFilename.c_str ());
    PointToPointHelper::EnableAscii (traceFile, NodeContainer (grid.GetNode (n/2, n/2)));
    PointToPointHelper::EnablePcap ("deflection-complex-behavior-" + runId.str (), NodeContainer (grid.GetNode (n/2, n/2)));
    NS_LOG_DEBUG ("Traces capturing enabled");
  } else {
    NS_LOG_DEBUG ("Not to output Pcap traces and Ascii traces");
  }

  // Create the animation object and configure for specified output
  AnimationInterface anim;
  if (outputTracesCmd)
    {
      // write a trace file and let NetAnim to open it
      anim.SetOutputFile ("deflection-complex-behavior-anim-" + runId.str () + ".tr");
      anim.StartAnimation ();
      NS_LOG_DEBUG ("ns-3 animation trace capture enabled");
    }

  // create various counters to measure simulation results
  //
  // keep trace of the development over time the following events
  // - number of total packets sent
  // - number of total packets received
  // - number of total packets dropped
  //
  // Events are counted on the granularity of some time interval. This time interval
  // is calculated so that there are at least 100 intervals for the entire simulation period
  double interval = GetTimeInterval (numPkt, pktSize, dataRate, numTimeIntervalsCmd);
  // count the number of active packets (sent but not yet received nor dropped) in the network
  Ptr<HistogramCalculator> packetActive = Create<HistogramCalculator> (interval);
  packetActive->SetKey ("histogram-packet-active");
  data.AddDataCalculator (packetActive);
  // count the number of packet sent in each time interval
  Ptr<HistogramCalculator> packetSent = Create<HistogramCalculator> (interval);
  packetSent->SetKey ("histogram-packet-sent");
  TraceConnectCountPacketSentOverTime (packetSent, flowmon);
  data.AddDataCalculator (packetSent);
  // count the number of packet received in each time interval
  Ptr<HistogramCalculator> packetReceived = Create<HistogramCalculator> (interval);
  packetReceived->SetKey ("histogram-packet-received");
  TraceConnectCountPacketReceivedOverTime (packetReceived, flowmon);
  data.AddDataCalculator (packetReceived);
  // count the number of packet dropped in each time interval
  Ptr<HistogramCalculator> packetDropped = Create<HistogramCalculator> (interval);    
  packetDropped->SetKey ("histogram-packet-dropped");
  TraceConnectCountPacketDroppedOverTime (packetDropped, flowmon);
  data.AddDataCalculator (packetDropped);
  // total number of traffic flows generated
  Ptr<CounterCalculator<> > totalFlows = CreateObject<CounterCalculator<> > ();
  totalFlows->SetKey ("total-number-flows");
  data.AddDataCalculator (totalFlows);
  // total number of packets transmitted at the end of simulation
  Ptr<CounterCalculator<> > totalTxPackets = CreateObject<CounterCalculator<> > ();
  totalTxPackets->SetKey ("total-tx-packets");
  data.AddDataCalculator (totalTxPackets);
  // total number of packets received at the end of simulation
  Ptr<CounterCalculator<> >  totalRxPackets = CreateObject<CounterCalculator<> > ();
  totalRxPackets->SetKey ("total-rx-packets");
  data.AddDataCalculator (totalRxPackets);
  // total number of packets dropped at the end of simulation
  Ptr<CounterCalculator<> > totalDropPackets = CreateObject<CounterCalculator<> > ();
  totalDropPackets->SetKey ("total-drop-packets");
  data.AddDataCalculator (totalDropPackets);
  // total occurrence of deflection events at the end of simulation
  Ptr<CounterCalculator<> > totalDeflections = CreateObject<CounterCalculator<> > ();
  totalDeflections->SetKey ("total-tx-packets-deflections");
  TraceConnectForCountDeflection (totalDeflections, grid, n, n);
  data.AddDataCalculator  (totalDeflections);
  // total number of hopping among all packets at the end of simulations
  Ptr<MinMaxAvgTotalCalculator<> > txPacketTotalHopping = CreateObject<MinMaxAvgTotalCalculator<> > ();
  txPacketTotalHopping->SetKey ("total-tx-packets-hopping");
  TraceConnectForCountTxPacketTotalForwarding (txPacketTotalHopping, flowmon);
  data.AddDataCalculator (txPacketTotalHopping);
  // total number of hopping among only the arrived packets at the end of simulation
  Ptr<MinMaxAvgTotalCalculator<> > rxPacketTotalHopping = CreateObject<MinMaxAvgTotalCalculator<> > ();
  rxPacketTotalHopping->SetKey ("total-rx-packets-hopping");
  TraceConnectForCountRxPacketTotalForwarding (rxPacketTotalHopping, flowmon);
  data.AddDataCalculator (rxPacketTotalHopping);
  // total sum of packet lifetime at the end of simulation
  Ptr<TimeMinMaxAvgTotalCalculator> sumPacketLifetime = CreateObject<TimeMinMaxAvgTotalCalculator> ();
  sumPacketLifetime->SetKey ("sum-tx-packet-lifetime-nanosec");
  TraceConnectForCountTotalPacketTimelife (sumPacketLifetime, flowmon);
  data.AddDataCalculator (sumPacketLifetime);
  // total number of bytes sent across all links at the end of simulation
  Ptr<PacketSizeMinMaxAvgTotalCalculator> totalBytesAcrossLinks = CreateObject<PacketSizeMinMaxAvgTotalCalculator> ();
  totalBytesAcrossLinks->SetKey ("total-bytes-across-links");
  TraceConnectForCountTotalBytesAcrossLinks (totalBytesAcrossLinks, grid, n, n);
  data.AddDataCalculator (totalBytesAcrossLinks);
  // time the first packet sent to a network
  Ptr<CounterCalculator<> > firstTxPktTime = CreateObject<CounterCalculator<> > ();
  firstTxPktTime->SetKey ("first-pkt-tx-time-nanosec");
  data.AddDataCalculator (firstTxPktTime);
  // time the last packet arrived from a network
  Ptr<CounterCalculator<> > lastRxPktTime = CreateObject<CounterCalculator<> > ();
  lastRxPktTime->SetKey ("last-pkt-rx-time-nanosec");
  data.AddDataCalculator (lastRxPktTime);

  // enable progress notification
  //NotifyProgressEvery (Seconds (stopTime/10.0));

  // kick on the simulation
  //Simulator::Stop (Seconds (stopTime*10.0));
  NS_LOG_DEBUG ("Simulation starts");
  Simulator::Run ();
  NS_LOG_DEBUG ("Simulation ends");

  if (outputTracesCmd)
    {
      // end the animation
      anim.StopAnimation ();
    }

  // record the attributes of this simulation  
  time_t runEndTime = time (NULL);
  string runEndTimeStr (ctime (&runEndTime));
  runEndTimeStr.erase (runEndTimeStr.find ('\n'));
  data.AddMetadata ("Simulation ends on", runEndTimeStr);

  // count the number of active packets over time
  CountNumActivePacketOverTime (PeekPointer (packetActive), PeekPointer (packetSent), 
                                PeekPointer (packetReceived), PeekPointer (packetDropped));

  // start calculating the overall measurement results by going through all flows' statistics
  typedef map<FlowId, FlowMonitor::FlowStats> FlowStatsMap_t;
  const FlowStatsMap_t& flowStatsContainer = flowmon->GetFlowStats ();
  Time minTime = Simulator::GetMaximumSimulationTime (); // to find the minimum tx time among all packets
  Time maxTime = Seconds (0); // to find the maximum rx time among all packets
  for (FlowStatsMap_t::const_iterator it = flowStatsContainer.begin (); it != flowStatsContainer.end (); it++) {
    FlowMonitor::FlowStats flowStats = it->second;

    totalTxPackets->Update (flowStats.txPackets);
    totalRxPackets->Update (flowStats.rxPackets);
    totalDropPackets->Update (flowStats.lostPackets);
    // FlowMonitor does not include the last hop into its 'timesForwarded' counter. The actual number of hops
    // can be obtained by adding 'rxPackets' to 'timesForwarded'
    rxPacketTotalHopping->Update (flowStats.rxPackets);
    totalFlows->Update ();
    // to find the time the first packet sent to a network and the time the last packet laft from a network
    minTime = min (minTime, flowStats.timeFirstTxPacket);
    maxTime = max (maxTime, flowStats.timeLastRxPacket);
  }
  firstTxPktTime->Update (minTime.GetNanoSeconds ());
  lastRxPktTime->Update (maxTime.GetNanoSeconds ());

  // output flow monitor results
  flowmon->SerializeToXmlFile ("flowmon-complex-behavior-" + runId.str () + ".xml", true, true);
  NS_LOG_DEBUG ("Flow monitor results serialized into a XML file");
  WriteGraph ("complex-behavior-graph-" + runId.str () + ".txt", Seconds (stopTime), false);
  NS_LOG_DEBUG ("Graph results wrote into a file");
  // output measurement results
  Ptr<DataOutputInterface> output;
  if (enableSqliteOutputCmd)
    output = CreateObject<SqliteDataOutput> ();
  else
    output = CreateObject<OmnetDataOutput> ();
  output->Output(data);

  NS_LOG_INFO ("Finished simulation pass: " << runId.str ());

  // delete objects to reduce memory footprint
  flowmon->Dispose ();
}

int 
main (int argc, char *argv[])
{
  // enable the command line argument parser provided by ns-3
  // to run multiple simulation, all of the following three values cannot be zero
  double dataLinkUtilStartCmd = 0.0; // multiple simulations on different data link utilization. start with this value
  double dataLinkUtilEndCmd = 0.0; // muliple simulation on different data link utilization. end at this value
  uint32_t dataLinkUtilStepsCmd = 1; // number of simulation to run
  bool dataLinkUtilLogScale = true; // set to true if the dataRate is incremented logarithmically from one simulation to the next
  CommandLine cmd;

  TestRunnerMain (argc, argv);

  cmd.AddValue ("EnableEcmp", "Set to true to enable ECMP routing on all nodes", enableEcmpCmd);
  cmd.AddValue ("EnableDeflection", "Set to true to enable deflection routing on all nodes", enableDeflectionCmd);

  cmd.AddValue ("FlowLength", "The length of each traffic flow, in term of number of links", flowLengthCmd);
  cmd.AddValue ("LineSpeed", "A string to specify line speed of all links", lineSpeedCmd);
  cmd.AddValue ("LineDelay", "A string to specify the line delay of all links", lineDelayCmd);  
  cmd.AddValue ("QueueMaxPackets", "Maximum number of packets in each queue", queueMaxPktCmd);
  cmd.AddValue ("PacketSize", "Size (byte) of all packets", pktSizeCmd);
  cmd.AddValue ("DataRate", "Data rate of all sources in term of the fraction of line speed", dataLinkUtilCmd);  
  cmd.AddValue ("MaxPacketsNumber", "Number of packets to generate. Set to 0 for unlimited number of packets", maxNumPacketsCmd);

  cmd.AddValue ("DataRateStart", "Specify the starting value of DataRate when runing muliple simulations", dataLinkUtilStartCmd);  
  cmd.AddValue ("DataRateEnd", "Specify the ending value of DataRate when runing muliple simulations", dataLinkUtilEndCmd);  
  cmd.AddValue ("DataRateStep", "Specify the number of muliple simulations to run", dataLinkUtilStepsCmd);
  cmd.AddValue ("DataRateLogScale", "Set to true if the DataRate is incremented logarithmically", dataLinkUtilLogScale);

  cmd.AddValue ("BackgroundRate", "(not used) Background traffic rate on all links in term of the fraction of line speed", backgroundLinkUtil);  
  cmd.AddValue ("EndTime", "A string to specify the end time of the simulation", endTimeCmd);  

  cmd.AddValue ("EnableSqlite3Output", "Set to true to write results into a sqlite3 database", enableSqliteOutputCmd);
  cmd.AddValue ("EnableOutputTraces", "Set to true to output several trace files", outputTracesCmd);
  cmd.AddValue ("TimeIntervalNumber", "Set the number of time intervals for dividing the entire simulation period when counting events over time", numTimeIntervalsCmd);

  cmd.AddValue ("test", "Start the embedded test-runner", MakeNullCallback<bool, string> ());

  cmd.Parse (argc, argv);

  NS_LOG_DEBUG ("Command line arguments parsed");  
  NS_ASSERT_MSG (dataLinkUtilCmd >= 0.0, "DataRate fraction cannot below 0.0");
  NS_ASSERT_MSG (backgroundLinkUtil >= 0.0, "BackgroundRate fraction cannot below 0.0");

  for (uint32_t repeat=0; repeat<dataLinkUtilStepsCmd; repeat++) {    
    NS_LOG_INFO ("running " << repeat+1 << " of " << dataLinkUtilStepsCmd << " simulation");
    // make sure random variable are iid between different simulation runs
    SeedManager::SetRun (repeat+1);
    // set the line speed of each link
    DataRate lineSpeed (lineSpeedCmd);
    // set the data rate for this simulation pass
    DataRate dataRate;
    if (dataLinkUtilStartCmd == 0 || dataLinkUtilEndCmd == 0) {
      // simulation is run once only. dataRate is set to the 'DataRate' from the command line
      dataRate = DataRate (lineSpeed.GetBitRate () * dataLinkUtilCmd); 
      NS_LOG_DEBUG ("One simulation only: DataRate = " << dataRate);
    } else {
      NS_ASSERT_MSG (dataLinkUtilEndCmd > dataLinkUtilStartCmd, 
        "Invalid input! dataLinkUtilEndCmd has to be strickly larger than dataLinkUtilStartCmd");
      // simulation is run muliple times. dataRate is set progresively from 'dataLinkUtilStartCmd' to 'dataLinkUtilEndCmd'
      NS_ASSERT_MSG (dataLinkUtilStepsCmd >= 2, "At least 2 simulation steps are needed for multiple simulation runs");
      // set the data rate to its next value either linearly or logarithmically
      if (dataLinkUtilLogScale) {
        // logarithmically
        double step = pow (dataLinkUtilEndCmd / dataLinkUtilStartCmd, 1.0 / (dataLinkUtilStepsCmd-1));    
        NS_LOG_DEBUG ("log: step = " << step);
        dataRate = DataRate (lineSpeed.GetBitRate () * (dataLinkUtilStartCmd * pow (step, (double)repeat))); 
      } else {
        // linearly
        double step = (dataLinkUtilEndCmd - dataLinkUtilStartCmd) / (dataLinkUtilStepsCmd - 1);
        dataRate = DataRate (lineSpeed.GetBitRate () * (dataLinkUtilStartCmd + step * (double)repeat)); 
      }
      NS_LOG_DEBUG ("Multiple simulations: DataRate = " << dataRate);
    }
    NS_LOG_DEBUG ("DataRate = " << dataRate);

    // create a n x n topology
    uint32_t n = 7;
    PointToPointHelper p2p;
    p2p.SetChannelAttribute ("Delay", TimeValue (Time (lineDelayCmd)));
    p2p.SetDeviceAttribute ("DataRate", DataRateValue (lineSpeedCmd));
    p2p.SetQueue ("ns3::DropTailQueue", 
      "Mode", EnumValue (DropTailQueue::PACKETS),
      "MaxPackets", UintegerValue (queueMaxPktCmd));
    PointToPointGridHelper grid (n, n, p2p);
    // Set the bounding box for animation
    grid.BoundingBox (1, 1, 10, 10);
    NS_LOG_DEBUG ("Topology created");
#if 0
    // The PointToPointGridHelper does not close the grid edges, so
    // I need to do it by myself
    CloseGridEdge (n, n, p2p, grid);
#endif
    // install to all nodes the protocols necessary for routing simulation  
    Ipv4DeflectionRoutingHelper dfRouting;
    Config::SetDefault ("ns3::Ipv4GlobalRouting::RandomEcmpRouting", BooleanValue (enableEcmpCmd));
    InternetStackHelper internet;
    if (enableDeflectionCmd) {internet.SetRoutingHelper (dfRouting.GetIpv4ListRoutingHelper ());}
    internet.SetIpv6StackInstall (false);
    grid.InstallStack (internet);
    NS_LOG_DEBUG ("Internet stack installed on all nodes");

    // assign IP addresses to the grid
    Ipv4AddressHelper rowIps ("10.0.0.0", "255.255.255.0");
    Ipv4AddressHelper colIps ("20.0.0.0", "255.255.255.0");
    grid.AssignIpv4Addresses (rowIps, colIps);
    NS_LOG_DEBUG ("IP addresses assigned to all net devices"); 

    // create routing domain for the entire grid
    if (enableDeflectionCmd) {
      Ipv4DeflectionRoutingHelper::PopulateRoutingTables ();
      NS_LOG_DEBUG ("Deflection routing tables populated");
    } else {
      Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
      NS_LOG_DEBUG ("Global routing tables populated");
    }

    // run one pass of the simulation
    RunSimulation (grid, n, dataRate);
    Simulator::Destroy ();
    Names::Clear ();
  }

  NS_LOG_DEBUG ("Simulation quits");
  return 0;
}
